import type Konva from 'konva'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import {
  Layer,
  Line as KonvaLine,
  Rect,
  RegularPolygon,
  Stage,
  Star,
  Transformer
} from 'react-konva'
import type { KonvaEventObject } from 'konva/lib/Node'
import styles from './styles.module.css'

const BASE_WIDTH = 750
const BASE_HEIGHT = 1334

type Tool = 'pen' | 'square' | 'triangle' | 'star' | 'diamond'
type ShapeKind = Extract<Tool, 'square' | 'triangle' | 'star' | 'diamond'>

interface LineRecord {
  id: string
  points: number[]
  stroke: string
  strokeWidth: number
}

interface ShapeRecord {
  id: string
  kind: ShapeKind
  x: number
  y: number
  size: number
  rotation: number
  fill: string
  stroke: string
  strokeWidth: number
}

interface Snapshot {
  lines: LineRecord[]
  shapes: ShapeRecord[]
}

const BRUSH_SIZES = [2, 4, 6, 10]
const BRUSH_COLORS = ['#204cff', '#ff6b6b', '#00b894', '#ffb341', '#222428']
const SCALE_RANGE = { min: 0.4, max: 3 }

const clamp = (value: number, min: number, max: number) => Math.min(Math.max(value, min), max)

const simplifyPath = (points: number[], tolerance = 5) => {
  if (points.length <= 4) {
    return points
  }

  const pointPairs: Array<{ x: number; y: number }> = []
  for (let i = 0; i < points.length; i += 2) {
    pointPairs.push({ x: points[i], y: points[i + 1] })
  }

  const sqTolerance = tolerance * tolerance

  const getSqDistance = (p1: { x: number; y: number }, p2: { x: number; y: number }) => {
    const dx = p1.x - p2.x
    const dy = p1.y - p2.y
    return dx * dx + dy * dy
  }

  const getSqSegDist = (
    point: { x: number; y: number },
    segmentStart: { x: number; y: number },
    segmentEnd: { x: number; y: number }
  ) => {
    let x = segmentStart.x
    let y = segmentStart.y
    let dx = segmentEnd.x - x
    let dy = segmentEnd.y - y

    if (dx !== 0 || dy !== 0) {
      const t = ((point.x - x) * dx + (point.y - y) * dy) / (dx * dx + dy * dy)
      if (t > 1) {
        x = segmentEnd.x
        y = segmentEnd.y
      } else if (t > 0) {
        x += dx * t
        y += dy * t
      }
    }

    dx = point.x - x
    dy = point.y - y

    return dx * dx + dy * dy
  }

  const simplifyRadialDistance = (pointsToSimplify: Array<{ x: number; y: number }>) => {
    const prevPoint = pointsToSimplify[0]
    const newPoints = [prevPoint]
    let currentPoint = prevPoint

    for (let i = 1; i < pointsToSimplify.length; i += 1) {
      currentPoint = pointsToSimplify[i]
      if (getSqDistance(currentPoint, newPoints[newPoints.length - 1]) > sqTolerance) {
        newPoints.push(currentPoint)
      }
    }

    if (newPoints[newPoints.length - 1] !== currentPoint) {
      newPoints.push(currentPoint)
    }

    return newPoints
  }

  const simplifyDouglasPeucker = (pointsToSimplify: Array<{ x: number; y: number }>) => {
    const lastIndex = pointsToSimplify.length - 1
    const markers = new Uint8Array(pointsToSimplify.length)

    const stack: Array<{ first: number; last: number }> = []
    const newPoints: Array<{ x: number; y: number }> = []

    const first = 0
    const last = lastIndex
    let index = 0

    markers[first] = 1
    markers[last] = 1

    stack.push({ first, last })

    while (stack.length > 0) {
      const { first: stackFirst, last: stackLast } = stack.pop() as {
        first: number
        last: number
      }

      let maxSqDist = 0

      for (let i = stackFirst + 1; i < stackLast; i += 1) {
        const sqDist = getSqSegDist(pointsToSimplify[i], pointsToSimplify[stackFirst], pointsToSimplify[stackLast])
        if (sqDist > maxSqDist) {
          index = i
          maxSqDist = sqDist
        }
      }

      if (maxSqDist > sqTolerance) {
        markers[index] = 1
        stack.push({ first: stackFirst, last: index })
        stack.push({ first: index, last: stackLast })
      }
    }

    for (let i = 0; i <= lastIndex; i += 1) {
      if (markers[i]) {
        newPoints.push(pointsToSimplify[i])
      }
    }

    return newPoints
  }

  const simplified = simplifyDouglasPeucker(simplifyRadialDistance(pointPairs))

  if (simplified.length < 2) {
    const firstPoint = pointPairs[0]
    const lastPoint = pointPairs[pointPairs.length - 1]
    return [firstPoint.x, firstPoint.y, lastPoint.x, lastPoint.y]
  }

  if (simplified.length === 2) {
    const [start, end] = simplified
    return [start.x, start.y, end.x, end.y]
  }

  const flattened: number[] = []
  simplified.forEach((pt) => {
    flattened.push(pt.x, pt.y)
  })

  return flattened
}

const createShapeRecord = (kind: ShapeKind, position: { x: number; y: number }): ShapeRecord => {
  const id = crypto.randomUUID()
  const baseSize = 160

  return {
    id,
    kind,
    x: position.x,
    y: position.y,
    size: baseSize,
    rotation: kind === 'diamond' ? 45 : 0,
    fill: 'rgba(74,108,247,0.12)',
    stroke: '#4a6cf7',
    strokeWidth: 4
  }
}

const PointMapDrawerPage = () => {
  const stageRef = useRef<Konva.Stage>(null)
  const drawingLayerRef = useRef<Konva.Layer>(null)
  const transformerRef = useRef<Konva.Transformer>(null)
  const containerRef = useRef<HTMLDivElement>(null)
  const [viewportScale, setViewportScale] = useState(0.5)
  const [viewScale, setViewScale] = useState(1)
  const [lines, setLines] = useState<LineRecord[]>([])
  const [shapes, setShapes] = useState<ShapeRecord[]>([])
  const [history, setHistory] = useState<Snapshot[]>([])
  const [activeTool, setActiveTool] = useState<Tool>('pen')
  const [brushColor, setBrushColor] = useState(BRUSH_COLORS[0])
  const [brushSize, setBrushSize] = useState(BRUSH_SIZES[1])
  const [selectedShapeId, setSelectedShapeId] = useState<string | null>(null)
  const drawingLineRef = useRef<{ id: string; points: number[] } | null>(null)
  const pinchStateRef = useRef<{ distance: number; scale: number } | null>(null)

  const stageScale = viewportScale * viewScale

  const hasContent = lines.length > 0 || shapes.length > 0

  const snapshot = useCallback((): Snapshot => {
    return {
      lines: lines.map((line) => ({ ...line, points: [...line.points] })),
      shapes: shapes.map((shape) => ({ ...shape }))
    }
  }, [lines, shapes])

  const pushHistory = useCallback(() => {
    setHistory((prev) => [...prev, snapshot()])
  }, [snapshot])

  const updateViewportScale = useCallback(() => {
    const element = containerRef.current
    if (!element) return

    const padding = 24
    const availableWidth = Math.max(element.clientWidth - padding, 280)
    const baseScale = availableWidth / BASE_WIDTH
    const boundedScale = clamp(baseScale, 0.38, 1.1)
    setViewportScale(boundedScale)
  }, [])

  useEffect(() => {
    updateViewportScale()
    const element = containerRef.current
    if (!element) return

    const observer = new ResizeObserver(() => updateViewportScale())
    observer.observe(element)

    return () => observer.disconnect()
  }, [updateViewportScale])

  useEffect(() => {
    const transformer = transformerRef.current
    const stage = stageRef.current
    if (!transformer || !stage) {
      return
    }

    if (!selectedShapeId) {
      transformer.nodes([])
      transformer.getLayer()?.batchDraw()
      return
    }

    const targetNode = stage.findOne(`#${selectedShapeId}`)
    if (targetNode) {
      transformer.nodes([targetNode as Konva.Node])
      transformer.getLayer()?.batchDraw()
    } else {
      transformer.nodes([])
      transformer.getLayer()?.batchDraw()
    }
  }, [selectedShapeId, shapes])

  const getPointerPosition = useCallback(() => {
    const stage = stageRef.current
    if (!stage) return null
    const pointer = stage.getPointerPosition()
    if (!pointer) return null

    return {
      x: pointer.x / stageScale,
      y: pointer.y / stageScale
    }
  }, [stageScale])

  const beginDrawing = useCallback(() => {
    const position = getPointerPosition()
    if (!position) return

    pushHistory()

    const id = crypto.randomUUID()
    const startingPoints = [position.x, position.y]
    const line: LineRecord = {
      id,
      points: startingPoints,
      stroke: brushColor,
      strokeWidth: brushSize
    }

    drawingLineRef.current = { id, points: startingPoints }
    setLines((prev) => [...prev, line])
  }, [brushColor, brushSize, getPointerPosition, pushHistory])

  const appendDrawingPoint = useCallback(() => {
    const current = drawingLineRef.current
    if (!current) return

    const position = getPointerPosition()
    if (!position) return

    const updatedPoints = [...current.points, position.x, position.y]
    drawingLineRef.current = { ...current, points: updatedPoints }

    setLines((prev) =>
      prev.map((line) =>
        line.id === current.id
          ? {
              ...line,
              points: updatedPoints
            }
          : line
      )
    )
  }, [getPointerPosition])

  const endDrawing = useCallback(() => {
    const current = drawingLineRef.current
    if (!current) return

    const simplifiedPoints = simplifyPath(current.points)

    setLines((prev) =>
      prev.map((line) => (line.id === current.id ? { ...line, points: simplifiedPoints } : line))
    )

    drawingLineRef.current = null
  }, [])

  const handleStagePointerDown = useCallback(
    (event: KonvaEventObject<PointerEvent>) => {
      const target = event.target
      const stage = stageRef.current
      const layer = drawingLayerRef.current
      if (!stage) return

      const isOnBackground = target === stage || target === layer

      if (!isOnBackground) {
        return
      }

      setSelectedShapeId(null)

      if (activeTool === 'pen') {
        beginDrawing()
        return
      }

      if (activeTool !== 'pen') {
        const position = getPointerPosition()
        if (!position) return
        pushHistory()
        const nextShape = createShapeRecord(activeTool, position)
        setShapes((prev) => [...prev, nextShape])
        setSelectedShapeId(nextShape.id)
      }
    },
    [activeTool, beginDrawing, getPointerPosition, pushHistory]
  )

  const handleStagePointerMove = useCallback(() => {
    if (!drawingLineRef.current) return
    appendDrawingPoint()
  }, [appendDrawingPoint])

  const handleStagePointerUp = useCallback(() => {
    if (!drawingLineRef.current) return
    endDrawing()
  }, [endDrawing])

  const handleWheel = useCallback(
    (event: KonvaEventObject<WheelEvent>) => {
      event.evt.preventDefault()
      const scaleBy = 1.04
      const direction = event.evt.deltaY > 0 ? -1 : 1
      const nextScale = direction > 0 ? viewScale * scaleBy : viewScale / scaleBy
      setViewScale(clamp(nextScale, SCALE_RANGE.min, SCALE_RANGE.max))
    },
    [viewScale]
  )

  const getTouchDistance = (touch1: Touch, touch2: Touch) => {
    const dx = touch2.clientX - touch1.clientX
    const dy = touch2.clientY - touch1.clientY
    return Math.sqrt(dx * dx + dy * dy)
  }

  const handleTouchStart = useCallback((event: KonvaEventObject<TouchEvent>) => {
    const touches = event.evt.touches
    if (touches.length === 2) {
      pinchStateRef.current = {
        distance: getTouchDistance(touches[0], touches[1]),
        scale: viewScale
      }
    }
  }, [viewScale])

  const handleTouchMove = useCallback((event: KonvaEventObject<TouchEvent>) => {
    const touches = event.evt.touches
    if (touches.length === 2 && pinchStateRef.current) {
      event.evt.preventDefault()
      const distance = getTouchDistance(touches[0], touches[1])
      const base = pinchStateRef.current
      const ratio = distance / base.distance
      const nextScale = clamp(base.scale * ratio, SCALE_RANGE.min, SCALE_RANGE.max)
      setViewScale(nextScale)
    }
  }, [])

  const handleTouchEnd = useCallback((event: KonvaEventObject<TouchEvent>) => {
    if (event.evt.touches.length < 2) {
      pinchStateRef.current = null
    }
    if (drawingLineRef.current) {
      endDrawing()
    }
  }, [endDrawing])

  const handleUndo = useCallback(() => {
    setHistory((prev) => {
      if (prev.length === 0) return prev
      const nextHistory = [...prev]
      const previousState = nextHistory.pop() as Snapshot
      setLines(previousState.lines.map((line) => ({ ...line, points: [...line.points] })))
      setShapes(previousState.shapes.map((shape) => ({ ...shape })))
      setSelectedShapeId(null)
      return nextHistory
    })
  }, [])

  const handleClear = useCallback(() => {
    if (!hasContent) return
    pushHistory()
    setLines([])
    setShapes([])
    setSelectedShapeId(null)
  }, [hasContent, pushHistory])

  const handleSave = useCallback(() => {
    const stage = stageRef.current
    if (!stage) return

    const dataUrl = stage.toDataURL({ pixelRatio: 2 })
    const link = document.createElement('a')
    link.href = dataUrl
    link.download = `pointmap-${Date.now()}.png`

    if (typeof link.download === 'string') {
      document.body.appendChild(link)
      link.click()
      document.body.removeChild(link)
    } else {
      window.open(dataUrl, '_blank')
    }
  }, [])

  const handleShapeDragMove = useCallback((shapeId: string, node: Konva.Shape) => {
    setShapes((prev) =>
      prev.map((shape) =>
        shape.id === shapeId
          ? {
              ...shape,
              x: node.x(),
              y: node.y()
            }
          : shape
      )
    )
  }, [])

  const handleShapeTransformEnd = useCallback((shape: ShapeRecord, node: Konva.Shape) => {
    const scaleX = node.scaleX()
    const scaleY = node.scaleY()
    const rotation = node.rotation()
    const nextSize = shape.size * ((scaleX + scaleY) / 2)

    node.scaleX(1)
    node.scaleY(1)

    setShapes((prev) =>
      prev.map((item) =>
        item.id === shape.id
          ? {
              ...item,
              x: node.x(),
              y: node.y(),
              rotation,
              size: Math.max(60, nextSize)
            }
          : item
      )
    )
  }, [])

  const stageDimensions = useMemo(() => {
    return {
      width: BASE_WIDTH * stageScale,
      height: BASE_HEIGHT * stageScale
    }
  }, [stageScale])

  return (
    <div className={styles.page}>
      <header className={styles.topBar}>
        <div className={styles.topLeft}>
          <button
            className={styles.navButton}
            onClick={() => window.history.back?.()}
            type="button"
          >
            返回
          </button>
          <div className={styles.titleBlock}>
            <h1 className={styles.title}>点位地图绘制</h1>
            <span className={styles.subtitle}>移动端手绘 · 等比例缩放 · 标记管理</span>
          </div>
        </div>
        <div className={styles.actionGroup}>
          <button
            className={styles.actionButton}
            onClick={handleUndo}
            disabled={history.length === 0}
            type="button"
          >
            撤回
          </button>
          <button
            className={styles.actionButton}
            onClick={handleClear}
            disabled={!hasContent}
            type="button"
          >
            清空
          </button>
          <button className={styles.actionPrimary} onClick={handleSave} type="button">
            保存
          </button>
        </div>
      </header>

      <main className={styles.mainArea}>
        <aside className={styles.toolPanel}>
          <div className={styles.toolGroupTitle}>绘制</div>
          <button
            className={`${styles.toolButton} ${activeTool === 'pen' ? styles.toolButtonActive : ''}`}
            onClick={() => setActiveTool('pen')}
            type="button"
          >
            画笔
          </button>
          <div className={styles.toolGroupTitle}>图形</div>
          {(['square', 'triangle', 'star', 'diamond'] as ShapeKind[]).map((shape) => (
            <button
              key={shape}
              className={`${styles.toolButton} ${activeTool === shape ? styles.toolButtonActive : ''}`}
              onClick={() => setActiveTool(shape)}
              type="button"
            >
              {shape === 'square' && '正方形'}
              {shape === 'triangle' && '三角形'}
              {shape === 'star' && '五角星'}
              {shape === 'diamond' && '菱形'}
            </button>
          ))}
        </aside>

        <div className={styles.canvasContainer} ref={containerRef}>
          <div
            className={styles.stageHolder}
            style={{ width: `${stageDimensions.width}px`, height: `${stageDimensions.height}px` }}
          >
            <Stage
              ref={stageRef}
              width={BASE_WIDTH}
              height={BASE_HEIGHT}
              scaleX={stageScale}
              scaleY={stageScale}
              className={styles.stage}
              onPointerDown={handleStagePointerDown}
              onPointerMove={handleStagePointerMove}
              onPointerUp={handleStagePointerUp}
              onPointerLeave={handleStagePointerUp}
              onWheel={handleWheel}
              onTouchStart={handleTouchStart}
              onTouchMove={handleTouchMove}
              onTouchEnd={handleTouchEnd}
            >
              <Layer ref={drawingLayerRef}>
                {lines.map((line) => (
                  <KonvaLine
                    key={line.id}
                    points={line.points}
                    stroke={line.stroke}
                    strokeWidth={line.strokeWidth}
                    lineCap="round"
                    lineJoin="round"
                  />
                ))}
                {shapes.map((shape) => {
                  const commonProps = {
                    key: shape.id,
                    id: shape.id,
                    x: shape.x,
                    y: shape.y,
                    stroke: shape.stroke,
                    strokeWidth: shape.strokeWidth,
                    fill: shape.fill,
                    rotation: shape.rotation,
                    draggable: true,
                    onClick: (event: KonvaEventObject<MouseEvent>) => {
                      event.cancelBubble = true
                      setSelectedShapeId(shape.id)
                    },
                    onTap: (event: KonvaEventObject<TouchEvent>) => {
                      event.cancelBubble = true
                      setSelectedShapeId(shape.id)
                    },
                    onDragStart: () => {
                      pushHistory()
                      setSelectedShapeId(shape.id)
                    },
                    onDragMove: (event: KonvaEventObject<DragEvent>) => {
                      handleShapeDragMove(shape.id, event.target as Konva.Shape)
                    },
                    onTransformEnd: (event: KonvaEventObject<Event>) => {
                      pushHistory()
                      handleShapeTransformEnd(shape, event.target as Konva.Shape)
                    }
                  }

                  if (shape.kind === 'square') {
                    return (
                      <Rect
                        {...commonProps}
                        width={shape.size}
                        height={shape.size}
                        offsetX={shape.size / 2}
                        offsetY={shape.size / 2}
                        cornerRadius={20}
                        shadowBlur={14}
                        shadowColor="rgba(74,108,247,0.25)"
                      />
                    )
                  }

                  if (shape.kind === 'triangle') {
                    return (
                      <RegularPolygon
                        {...commonProps}
                        sides={3}
                        radius={shape.size / 2}
                        shadowBlur={12}
                        shadowColor="rgba(74,108,247,0.2)"
                      />
                    )
                  }

                  if (shape.kind === 'star') {
                    return (
                      <Star
                        {...commonProps}
                        numPoints={5}
                        innerRadius={(shape.size / 2) * 0.48}
                        outerRadius={shape.size / 2}
                        shadowBlur={12}
                        shadowColor="rgba(74,108,247,0.2)"
                      />
                    )
                  }

                  return (
                    <RegularPolygon
                      {...commonProps}
                      sides={4}
                      radius={shape.size / 2}
                      rotation={45}
                      shadowBlur={12}
                      shadowColor="rgba(74,108,247,0.2)"
                    />
                  )
                })}
                <Transformer
                  ref={transformerRef}
                  rotateEnabled
                  borderStroke="#4a6cf7"
                  borderStrokeWidth={2}
                  anchorStroke="#4a6cf7"
                  anchorFill="#ffffff"
                  anchorSize={8}
                  anchorStrokeWidth={2}
                  keepRatio
                />
              </Layer>
            </Stage>
          </div>
        </div>
      </main>

      <footer className={styles.bottomPanel}>
        <div className={styles.paletteRow}>
          <span className={styles.bottomLabel}>颜色</span>
          <div className={styles.palette}>
            {BRUSH_COLORS.map((color) => (
              <button
                key={color}
                className={`${styles.colorOption} ${brushColor === color ? styles.colorActive : ''}`}
                style={{ backgroundColor: color }}
                onClick={() => setBrushColor(color)}
                type="button"
              />
            ))}
          </div>
        </div>

        <div className={styles.sliderRow}>
          <label className={styles.bottomLabel} htmlFor="brushSize">
            线条粗细
          </label>
          <input
            id="brushSize"
            className={styles.sliderInput}
            type="range"
            min={BRUSH_SIZES[0]}
            max={BRUSH_SIZES[BRUSH_SIZES.length - 1]}
            step={1}
            value={brushSize}
            onChange={(event) => setBrushSize(Number(event.target.value))}
          />
          <span className={styles.sliderValue}>{brushSize}px</span>
        </div>

        <div className={styles.sliderRow}>
          <label className={styles.bottomLabel} htmlFor="viewScale">
            缩放
          </label>
          <input
            id="viewScale"
            className={styles.sliderInput}
            type="range"
            min={SCALE_RANGE.min}
            max={SCALE_RANGE.max}
            step={0.1}
            value={viewScale}
            onChange={(event) => setViewScale(Number(event.target.value))}
          />
          <span className={styles.sliderValue}>{Math.round(viewScale * 100)}%</span>
        </div>
      </footer>
    </div>
  )
}

export default PointMapDrawerPage

